define(
	["webgl", "cam", "glMatrix"], 
	function(webgl, ccam) {
	
		return {
			create : function() {
				var comps 		= [];
				var dead 		= [];
				var drawBatch	= []; //by opaque / not, by shader, by model, by distance to cam
				var drawn		= [];
				var undrawnFlag	= true;
				var tempMat3	= mat3.create();
				var gl 			= webgl.gl;
				
				(function init() {
					gl.clearColor(0,0,0,1);
					gl.enable(gl.DEPTH_TEST);
				})();
				
				function createComp(id) {
					return {
						transform 	: null,
						shader		: null,
						model		: null,
						material	: null,
						alpha		: 1.0,
						idx			: id,
						batchIdx	: null
					};
				}
				
				function findAvail() {
					for(var i = 0; i < dead.length; ++i) {
						if(dead[i]) return i;
					}
					return null;
				}
				
				function allocateNew() {
					var idx 	= comps.length;
					var comp	= createComp(idx);
					comps[idx] 	= comp;
					dead[idx] 	= false;
					drawn[idx]	= undrawnFlag;
					return comp;
				}
				
				function allocate(idx) {
					dead[idx]	= false;
					return comps[idx];
				}
		
				function pointAttrib(shader, attrib, buf) {
					gl.bindBuffer(gl.ARRAY_BUFFER, buf);
					gl.vertexAttribPointer(shader.getAttribute(attrib), buf.itemSize, gl.FLOAT, false, 0, 0);
				}
				
				function applyShader(shader, cam) {
					gl.useProgram(shader.program);
					gl.uniformMatrix4fv(shader.getUniform("uVMat"), false, cam.getView());
					gl.uniformMatrix4fv(shader.getUniform("uPMat"), false, cam.getPerspective());	
					
					//apply lights
					gl.uniform3fv(shader.getUniform("uLDir"), [-0.1,-0.5,-0.1]);
					gl.uniform3fv(shader.getUniform("uCamPos"), [0.1,0.5,0.1]);
					gl.uniform4fv(shader.getUniform("uLAmbient"), [0.1,0.1,0.1,1]);
					gl.uniform4fv(shader.getUniform("uLDiffuse"), [1,1,1,1]);
					gl.uniform4fv(shader.getUniform("uLSpecular"), [1,1,1,1]);
				}
				
				function applyModel(shader, model, material) {
					pointAttrib(shader, "aPos", model.buf.pos);
					pointAttrib(shader, "aNormal", model.buf.normal);
					
					//apply materials
					gl.uniform4fv(shader.getUniform("uMAmbient"), material.data.ambient);
					gl.uniform4fv(shader.getUniform("uMDiffuse"), material.data.diffuse);
					gl.uniform4fv(shader.getUniform("uMSpecular"), material.data.specular);
					gl.uniform1f(shader.getUniform("uMSpecPow"), material.data.specPow);
				}
				
				function applyTransforms(shader, transform) {
					//set transform
					gl.uniformMatrix4fv(shader.getUniform("uMMat"), false, transform.getMatrix());
					
					//normal transform
					mat4.toInverseMat3(transform.getMatrix(), tempMat3);
					mat3.transpose(tempMat3);
					gl.uniformMatrix3fv(shader.getUniform("uNMat"), false, tempMat3);
				}
				
				function canBeDrawn(comp) {
					return comp.model.isLoaded() && comp.shader.isLoaded() && comp.material.isLoaded();
				}
			
				return {
					cam		: ccam.create(),
				
					getComp : function() {
						var idx = findAvail();
						if(!idx)
							return allocateNew();
						else
							return allocate(idx);
					},
					
					letComp : function(comp) {
						dead[comp.id] = true;
					},
					
					render	: function() {
						gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
						
						var shader 	= null;
						var model	= null;
						
						for(var i = 0; i < comps.length; ++i) {
							var comp = comps[i];
							if(dead[i] || !canBeDrawn(comp)) {
								continue;
							}
								
							if(comp.shader != shader) {
								applyShader(shader = comp.shader, this.cam);
								applyModel(shader, model = comp.model, comp.material);
							}
							else if(comp.model != model) {
								applyModel(shader, model = comp.model, comp.material);
							}
							
							applyTransforms(shader, comp.transform);
							
							//Draw call
							gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.buf.indices);
							gl.drawElements(gl.LINES, model.buf.indices.numItems, gl.UNSIGNED_SHORT, 0);
						}
					}
				};
			}
		};
	}
);